/*******************************************************************************
 * DBS Projekt WS 11/12, 
 * Chris Borckholder, 
 * 1046730
 ******************************************************************************/
package at.ac.univie.ise.common;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import de.pyranja.util.Utils;

public class DBAccess {
	
	// logger
	private final Log log = LogFactory.getLog(DBAccess.class);
	
	// db connection parameter
	private String dburl;
	private Properties credentials;
	
	// connection pool
	private Queue<Connection> pool;
	
	// bean constructor
	public DBAccess() {
		this(Constants.DB_PROP_FILE);
	}
	
	// variable configuration
	public DBAccess(String propfile) {
		log.info("building DBAccess");
		try {
			Properties props = Utils.loadProps(propfile);
			this.prepareConnectionParameter(props);
			this.prepareConnectionPool(props);
		} catch (Exception e) {
			log.fatal("failed to initialize DBAccess",e);
			throw new IllegalStateException("DBAccess initialization failed",e);
		}
		log.info("DBAccess built");
	}
	
	public void terminate() {
		log.info("terminating connection pool");
		Connection closeMe = pool.poll();
		while ( closeMe != null ) {
			try {
				closeMe.close();
			} catch (Exception e) {
				log.warn("failed to close a connection on pool termination");
			}
			closeMe = pool.poll();
		}
		log.info("connection pool terminated");
	}

	// init helper ===========================================================>
	
	// loading and constructing connection parameters
	private void prepareConnectionParameter(Properties props) throws ClassNotFoundException {
		log.info("preparing connection parameter");
		// load jdbc driver
		Class.forName(props.getProperty(Constants.PREFIX_DB +".jdbc"));
		if ( log.isDebugEnabled() ) { log.debug("jdbc driver loaded"); }
		// build db url
		dburl = props.getProperty(Constants.PREFIX_DB +".url.base")+
				props.getProperty(Constants.PREFIX_DB +".url.name");
		if ( log.isDebugEnabled() ) { log.debug("built dburl as ["+ dburl +"]"); }
		// credentials to access db
		credentials = new Properties();
		credentials.setProperty("user", props.getProperty(Constants.PREFIX_DB +".user") );
		credentials.setProperty("password", props.getProperty(Constants.PREFIX_DB +".pw") );
		if ( log.isDebugEnabled() ) { log.debug("set credentials as user:"+ credentials.getProperty("user") +" pw:"+ credentials.getProperty("password") ); }
		log.info("connection parameters prepared");
	}

	// preparing connection pool using a blockingqueue
	private void prepareConnectionPool(Properties props) {
		log.info("preparing connection pool");
		// try to determine pool size
		int poolsize = Integer.parseInt( props.getProperty( Constants.PREFIX_DB +".poolsize") );
		if ( log.isDebugEnabled() ) { log.debug("using poolsize <"+ poolsize +">"); }
		pool = new LinkedBlockingQueue<Connection>(poolsize);
		log.info("connection pool prepared");
	}
	
	// connection pooling impl ===========================================================>
	/**
	 * Tries to assign the caller an existing pooled connection or a opens a new one if none is available.<br />
	 * Instead of closing the connection yourself, connections retrieved this way should always be returned
	 * via releaseConnection to allow reuse.
	 * @return a SQL connection
	 * @throws SQLException
	 */
	public Connection openConnection() throws SQLException {
		Connection out = pool.poll();
		if ( out == null ) {	// pool is empty
			log.trace("building new connection");
			out = DriverManager.getConnection(dburl, credentials);
		}
		log.trace("connection opened");
		return out;
	}
	
	/**
	 * Return a connection, allowing it to be freed if enough connections are pooled or reuse 
	 * @param releaseMe
	 * @throws SQLException
	 */
	public void releaseConnection(Connection releaseMe) throws SQLException {
		if ( !pool.offer(releaseMe) ) {
			log.trace("pool is full : connection closed");
			releaseMe.close();	// pool is full
		}
		log.trace("connection released");
	}
	
	// free resources
	protected void finalize() {
		try {
			super.finalize();
			this.terminate();
		} catch (Throwable e) {
			// well ...
		}
	}
}
